﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;

namespace Faml.Frontend
{
    public class FamlLambdaExpression : FamlExpression
    {
        private IList<FamlLambdaParameter> pars;
        private FamlExpression body;

        public FamlLambdaExpression(FamlExpression body, IList<FamlLambdaParameter> pars)
        {
            this.pars = pars;

            if (body.NodeType != FamlExpressionType.UnaryExpression ||
                ((FamlUnaryExpression)body).Operator != UnaryOperations.Quote)
                this.body = FamlUnaryExpression.MakeQuote(body, new ReadOnlyCollection<FamlLambdaParameter>(pars));
            else 
                this.body = body;

            MakeMetadata();
        }
               
        public FamlLambdaExpression(FamlExpression body, params FamlLambdaParameter[] pars)
        {
            this.pars = new List<FamlLambdaParameter>();
            foreach (FamlLambdaParameter par in pars)
                this.pars.Add(par);

            if (body.NodeType != FamlExpressionType.UnaryExpression ||
                ((FamlUnaryExpression)body).Operator != UnaryOperations.Quote)
                this.body = FamlUnaryExpression.MakeQuote(body, new ReadOnlyCollection<FamlLambdaParameter>(pars));
            else
                this.body = body;

            MakeMetadata();
        }

        public IList<FamlLambdaParameter> Parameters
        {
            get
            {
                return pars;
            }
        }

        public FamlExpression Body
        {
            get
            {
                return body;
            }
        }

        public override FamlExpression Expand()
        {
            return new FamlLambdaExpression(((FamlUnaryExpression)body).Target.Expand(), pars);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("(");
            for (int i = 0; i < pars.Count; i++)
            {
                sb.Append(pars[i].ToString());
                if (i < pars.Count - 1)
                    sb.Append(", ");
                else
                    sb.Append(")=>");
            }
            sb.Append(body.ToString());
            return sb.ToString();
        }

        protected override Type ComputeType()
        {
            Type delegateType = null;
            switch (pars.Count)
            {
                case 0:
                    delegateType = typeof(Func<>).MakeGenericType(body.Type);
                    break;
                case 1:
                    delegateType = typeof(Func<,>).MakeGenericType(pars[0].Type, body.Type);
                    break;
                case 2:
                    delegateType = typeof(Func<,,>).MakeGenericType(pars[0].Type, pars[1].Type, body.Type);
                    break;
                case 3:
                    delegateType = typeof(Func<,,,>).MakeGenericType(pars[0].Type, pars[1].Type, pars[2].Type, body.Type);
                    break;
                case 4:
                    delegateType = typeof(Func<,,,,>).MakeGenericType(pars[0].Type, pars[1].Type, pars[2].Type, pars[3].Type, body.Type);
                    break;
                default:
                    throw new NotSupportedException("Method is not supported for specified number of parameters");
            }
            return typeof(Expression<>).MakeGenericType(delegateType);
        }

        protected override void MakeMetadata()
        {
            int hash = NodeType.GetHashCode();
            int height = 1;

            foreach (FamlLambdaParameter item in pars)
            {
                hash ^= item.Hash;                
            }

            hash ^= body.Metadata.Hash;
            height = Math.Max(height, body.Metadata.Height + 1);
            foreach (FamlLambdaParameter p in body.NodeParameters)
            {
                if (!pars.Contains(p))
                    metadata.AddIfNotExist(p);
            }

            this.metadata.Hash = hash;
            this.metadata.Height = height;            
        }

        public override FamlExpressionType NodeType
        {
            get { return FamlExpressionType.Lambda; }
        }

        public override IEnumerable<FamlExpression> Children
        {
            get { yield return body; }
        }
    }

}
